package com.sniper.web.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;
import redis.clients.util.Pool;

import java.util.*;

/**
 * 本程序主要使用非切片连接池
 * redis速度对比，普通-》事物-》管道
 * 批量操作必须用管道去操作
 * 下面封装的操作方法，不建议在使用次数非常多的地方放调用，在调用次数多的地方建议使用jedis原始方法
 *
 * @author suzhen
 */
public class RedisConnUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisConnUtil.class);
    // 集群
    private JedisCluster cluster;
    private JedisShardInfo shardInfo;
    private String hostName = Protocol.DEFAULT_HOST;
    private int port = Protocol.DEFAULT_PORT;
    private int timeout = Protocol.DEFAULT_TIMEOUT;
    private String password;
    private boolean usePool = true;
    private int dbIndex = Protocol.DEFAULT_DATABASE;
    private String prefix;
    private JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 非切片连接池。单机
    private Pool<Jedis> jedisPool;
    // 切片连接池,适用于添加,分布式连接池+管道会很快
    private Pool<ShardedJedis> shardedJedisPool;
    private List<JedisShardInfo> shardInfos = new ArrayList<>();


    public boolean isUsePool() {
        return usePool;
    }

    public void setUsePool(boolean usePool) {
        this.usePool = usePool;
    }

    public void setCluster(JedisCluster cluster) {
        this.cluster = cluster;
    }

    public String getHostName() {
        return hostName;
    }

    public void setHostName(String hostName) {
        this.hostName = hostName;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public JedisPoolConfig getJedisPoolConfig() {
        return jedisPoolConfig;
    }

    public void setJedisPoolConfig(JedisPoolConfig jedisPoolConfig) {
        this.jedisPoolConfig = jedisPoolConfig;
    }

    public void setJedisPool(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public Pool<Jedis> getJedisPool() {
        if (jedisPool == null) {
            jedisPoolConfig.setMaxTotal(200);
            jedisPoolConfig.setMaxIdle(50);
            jedisPoolConfig.setMinIdle(8);//设置最小空闲数
            jedisPoolConfig.setMaxWaitMillis(10000);
            jedisPoolConfig.setTestOnBorrow(true);
            jedisPoolConfig.setTestOnReturn(true);
            //Idle时进行连接扫描
            jedisPoolConfig.setTestWhileIdle(true);
            //表示idle object evitor两次扫描之间要sleep的毫秒数
            jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
            //表示idle object evitor每次扫描的最多的对象数
            jedisPoolConfig.setNumTestsPerEvictionRun(10);
            //表示一个对象至少停留在idle状态的最短时间，然后才能被idle object evitor扫描并驱逐；这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
            jedisPoolConfig.setMinEvictableIdleTimeMillis(60000);
            jedisPool = new JedisPool(jedisPoolConfig, hostName);
        }
        return jedisPool;
    }

    public Pool<ShardedJedis> getShardedJedisPool() {
        if (shardedJedisPool == null) {
            shardedJedisPool = new ShardedJedisPool(jedisPoolConfig, getShardInfos());
        }
        return shardedJedisPool;
    }

    public void setShardedJedisPool(ShardedJedisPool shardedJedisPool) {
        this.shardedJedisPool = shardedJedisPool;
    }


    public JedisShardInfo getShardInfo() {
        if (shardInfo == null) {
            shardInfo = new JedisShardInfo(hostName, port);
            if (password != null) {
                shardInfo.setPassword(password);
            }
        }
        return shardInfo;
    }

    public void setShardInfos(List<JedisShardInfo> shardInfos) {
        this.shardInfos = shardInfos;
    }

    public void setJedisPool(Pool<Jedis> jedisPool) {
        this.jedisPool = jedisPool;
    }

    public List<JedisShardInfo> getShardInfos() {
        if (shardInfos.size() == 0) {
            shardInfos.add(getShardInfo());
        }
        return shardInfos;
    }

    public void setShardInfo(JedisShardInfo shardInfo) {
        this.shardInfo = shardInfo;
    }

    public RedisConnUtil() {

    }

    /**
     * 获取jedis
     *
     * @return
     */
    public Jedis getJedis() {
        Jedis jedis;
        if (usePool) {
            jedis = getJedisPool().getResource();
        } else {
            jedis = new Jedis(hostName, port, timeout);
        }
        jedis.connect();
        return jedis;
    }

    /**
     * 获取jedis
     *
     * @return
     */
    public ShardedJedis getShardedJedis() {

        ShardedJedis shardedJedis;
        if (usePool) {
            shardedJedis = getShardedJedisPool().getResource();
        } else {
            shardedJedis = new ShardedJedis(shardInfos);
        }
        return shardedJedis;
    }

    /**
     * jedis 集群
     *
     * @return
     */
    public JedisCluster getCluster() {
        if (cluster == null) {
            Set<HostAndPort> andPorts = new HashSet<>();
            for (JedisShardInfo info : getShardInfos()) {
                HostAndPort nodes = new HostAndPort(info.getHost(), info.getPort());
                andPorts.add(nodes);
            }
            cluster = new JedisCluster(andPorts);
        }
        return cluster;
    }

    /**
     * 获取key
     *
     * @param key
     * @return
     */
    public String getKey(String key) {
        if (key.startsWith(getPrefix())) {
            return key;
        }
        return prefix + key;
    }

    /**
     * 获取key
     *
     * @param key
     * @return
     */
    public byte[] getKey(byte[] key) {
        String keyS = new String(key);
        if (keyS.startsWith(prefix)) {
            return key;
        }
        return arraycat(prefix.getBytes(), key);
    }

    private byte[] arraycat(byte[] buf1, byte[] buf2) {
        byte[] bufert = null;
        int len1 = 0;
        int len2 = 0;
        if (buf1 != null) {
            len1 = buf1.length;
        }
        if (buf2 != null) {
            len2 = buf2.length;
        }
        if (len1 + len2 > 0) {
            bufert = new byte[len1 + len2];
        }
        if (len1 > 0) {
            System.arraycopy(buf1, 0, bufert, 0, len1);
        }
        if (len2 > 0) {
            System.arraycopy(buf2, 0, bufert, len1, len2);
        }
        return bufert;
    }

    /**
     * 返回
     *
     * @param object
     * @param method
     * @return
     */
    public String getKey(Class<?> object, String method) {
        return prefix + object.getName() + ":" + method;
    }


    /**
     * get value from redis
     *
     * @param key
     * @return
     */
    public byte[] get(byte[] key) {
        byte[] value;
        Jedis jedis = null;
        try {
            jedis = getJedis();
            value = jedis.get(getKey(key));
        } finally {
            close(jedis);
        }
        return value;
    }

    /**
     * 获取数据
     *
     * @param key
     * @return
     */
    public String get(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            key = getKey(key);
            if (this.exists(key)) {
                return jedis.get(key);
            }

        } finally {
            close(jedis);
        }
        return null;

    }

    /**
     * set
     *
     * @param key
     * @param value
     * @return
     */
    public String set(byte[] key, byte[] value, long milliseconds) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            byte[] key2 = getKey(key);
            String result = jedis.set(key2, value);
            if (milliseconds > 0 && result.toUpperCase().equals("OK")) {
                long l = jedis.pexpire(key2, milliseconds);
            }
            return result;
        } finally {
            close(jedis);
        }

    }

    /**
     * 设置
     *
     * @param key
     * @param value
     * @param milliseconds 毫秒
     */
    public String set(String key, String value, long milliseconds) {
        Jedis jedis = null;
        try {
            key = getKey(key);
            jedis = getJedis();
            String result = jedis.set(key, value);
            if (milliseconds != 0 && result.equals("OK")) {
                //单位毫秒
                jedis.pexpire(key, milliseconds);
            }
            return result;
        } finally {
            close(jedis);
        }
    }

    /**
     * 分布式+管道
     *
     * @param key
     * @param map
     * @param milliseconds
     */
    public void hmset(String key, Map<String, String> map, long milliseconds) {
        ShardedJedis shardedJedis = null;
        try {
            key = getKey(key);
            LOGGER.debug("写入key：" + key);
            shardedJedis = getShardedJedis();
            //管道 发布/订阅模式
            ShardedJedisPipeline pipeline = shardedJedis.pipelined();
            pipeline.hmset(key, map);
            if (milliseconds > 0) {
                pipeline.pexpire(key, milliseconds);
            }
            pipeline.syncAndReturnAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(shardedJedis);
        }
    }


    /**
     * @param key
     */
    public Set<byte[]> keys(byte[] key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.keys(getKey(key));
        } finally {
            close(jedis);
        }
    }

    public Set<String> keys(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.keys(getKey(key));
        } finally {
            close(jedis);
        }
    }

    public boolean exists(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.exists(getKey(key));
        } finally {
            close(jedis);
        }
    }

    public boolean exists(byte[] key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.exists(getKey(key));
        } finally {
            close(jedis);
        }
    }

    public Map<String, String> hgetAll(String key) {
        ShardedJedis shardedJedis = null;
        Map<String, String> map = new HashMap<>();
        key = getKey(key);
        LOGGER.debug("读取key：" + key);
        try {
            shardedJedis = getShardedJedis();
            if (shardedJedis.exists(key)) {
                map = shardedJedis.hgetAll(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(shardedJedis);
        }
        return map;
    }


    public long del(String key) {
        Jedis jedis = null;
        try {
            key = getKey(key);
            jedis = getJedis();
            return jedis.del(key);
        } finally {
            close(jedis);
        }
    }

    /**
     * del
     *
     * @param key
     */
    public long del(byte[] key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.del(getKey(key));
        } finally {
            close(jedis);
        }
    }

    public void delKeys(String key) {
        Jedis jedis = null;
        try {
            key = getKey(key);
            jedis = getJedis();
            Set<String> keySet = jedis.keys(key);
            Transaction tx = jedis.multi();
            for (String k : keySet) {
                tx.del(k);
            }
            tx.exec();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(jedis);
        }
    }

    public Long dbSize() {
        Long dbSize;
        Jedis jedis = null;
        try {
            jedis = getJedis();
            dbSize = jedis.dbSize();
        } finally {
            close(jedis);
        }
        return dbSize;
    }

    /**
     * 删除当前数据库所有key
     */
    public void flushDB() {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.flushDB();
        } finally {
            close(jedis);
        }
    }

    /**
     * 删除所有数据库key
     */
    public void flushAll() {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.flushAll();
        } finally {
            close(jedis);
        }
    }

    public void close(Jedis jedis) {
        if (jedis != null && jedis.isConnected()) {
            jedis.close();
        }
    }

    public void close(ShardedJedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}
